﻿using System;

namespace Sencha.Serialization
{
	public static class JsonReaderExtentions
	{
		public static void ReadArrayBegin(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token != JsonToken.BeginArray)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.BeginArray);
			if (reader.IsEndOfStream())
				throw new Exceptions.UnexpectedToken(reader, JsonToken.EndOfArray);

			if (advance)
				reader.NextToken();
		}
		public static void ReadArrayEnd(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token != JsonToken.EndOfArray)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.EndOfArray);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();
		}
		public static void ReadObjectBegin(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token != JsonToken.BeginObject)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.BeginObject);
			if (reader.IsEndOfStream())
				throw new Exceptions.UnexpectedToken(reader, JsonToken.EndOfObject);

			if (advance)
				reader.NextToken();
		}
		public static void ReadObjectEnd(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			if (reader.Token != JsonToken.EndOfObject)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.EndOfObject);
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();
		}
		public static string ReadMember(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token != JsonToken.Member)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.Member);

			var memberName = (string)reader.RawValue;

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return memberName;
		}
		public static T ReadMember<T>(this IJsonReader reader, string name, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			if (name == null)
				throw new ArgumentNullException("name");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token != JsonToken.Member)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.Member);

			var memberName = reader.RawValue as string;

			if (!string.Equals(memberName, name))
				throw new Exceptions.UnexpectedMemberName(memberName, name, reader);

			if (reader.IsEndOfStream())
				throw new Exceptions.UnexpectedToken(reader, JsonToken.EndOfObject);

			reader.NextToken();

			var readed = reader.ReadValue<T>(false);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return readed;
		}
		public static Byte ReadByte(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Byte);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsByte;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Byte? ReadByteOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Byte?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsByte;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static SByte ReadSByte(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(SByte);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsSByte;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static SByte? ReadSByteOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(SByte?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsSByte;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Int16 ReadInt16(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Int16);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsInt16;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Int16? ReadInt16OrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Int16?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsInt16;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Int32 ReadInt32(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Int32);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsInt32;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Int32? ReadInt32OrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Int32?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsInt32;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Int64 ReadInt64(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Int64);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsInt64;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Int64? ReadInt64OrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Int64?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsInt64;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static UInt16 ReadUInt16(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(UInt16);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsUInt16;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static UInt16? ReadUInt16OrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(UInt16?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsUInt16;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static UInt32 ReadUInt32(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(UInt32);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsUInt32;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static UInt32? ReadUInt32OrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(UInt32?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsUInt32;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static UInt64 ReadUInt64(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(UInt64);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsUInt64;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static UInt64? ReadUInt64OrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(UInt64?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsUInt64;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Single ReadSingle(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Single);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsSingle;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Single? ReadSingleOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Single?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsSingle;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Double ReadDouble(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Double);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsDouble;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Double? ReadDoubleOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Double?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsDouble;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Decimal ReadDecimal(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Decimal);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsDecimal;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Decimal? ReadDecimalOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Decimal?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsDecimal;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static Boolean ReadBoolean(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Boolean);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Boolean)
				value = reader.Value.AsBoolean;
			else
                throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Boolean);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static Boolean? ReadBooleanOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(Boolean?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
            else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Boolean)
				value = reader.Value.AsBoolean;
			else
                throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Boolean);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static DateTime ReadDateTime(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(DateTime);
			if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsDateTime;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static DateTime? ReadDateTimeOrNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(DateTime?);
			if (reader.Token == JsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == JsonToken.StringLiteral || reader.Token == JsonToken.Number)
				value = reader.Value.AsDateTime;
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral, JsonToken.Number);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;

		}
		public static string ReadString(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var stringValue =  default(string);

			if (reader.Token == JsonToken.Null)
				goto next;

			if (reader.Token != JsonToken.StringLiteral)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.StringLiteral);

			stringValue = reader.RawValue as string;

		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return stringValue;
		}
		public static void ReadNull(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token != JsonToken.Null)
				throw new Exceptions.UnexpectedToken(reader, JsonToken.Null);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();
		}
		public static ValueT ReadValue<ValueT>(this IJsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var rwFeature = reader.Context as IJsonSerializer;
			if (rwFeature == null)
				throw new Exceptions.JsonSerializationException(Loc.GetErrorString("Exception_ContextDoesNotSupportHelperMethod", typeof(ISerializationContext), typeof(IJsonSerializer), "ReadValue"), Exceptions.ErrorCode.GenericJsonSerializationException, reader);

			var knownType = reader.Context.DataContract.AddKnownType(typeof(ValueT));
			var value = rwFeature.ReadValue<ValueT>(reader, null);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}

		public static void ThrowUnexpectedToken(this IJsonReader reader, params JsonToken[] expectedTokens)
		{
			throw new Exceptions.UnexpectedToken(reader, expectedTokens);
		}
		public static void ThrowIfEndOfStream(this IJsonReader reader, JsonToken expectedToken)
		{
			if (reader.IsEndOfStream())
				throw new Exceptions.UnexpectedToken(reader, expectedToken);
		}
		public static void ThrowStackImbalance(IJsonReader reader)
		{
			throw new Exception("stack imbalance");
		}
		public static void ThrowIfMemberNameIsEmpty(IJsonReader reader, string memberName)
		{
			if (string.IsNullOrEmpty(memberName))
				throw new Exceptions.EmptyMemberName(reader);
		}
	}
}
